#include "../stdlib.hpp"
#include <purewell/code/base64.hpp>

namespace purewell
{

PWBase64& gPWBase64(PWBase64::getInstance());

static const char gPWBase64_enc[] = {
	'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
	'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
	'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
	'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
	'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
	'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
	'w', 'x', 'y', 'z', '0', '1', '2', '3',
	'4', '5', '6', '7', '8', '9', '+', '/' };

static const char gPWBase64_encURI[] = {
	'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
	'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
	'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
	'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
	'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
	'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
	'w', 'x', 'y', 'z', '0', '1', '2', '3',
	'4', '5', '6', '7', '8', '9', '-', '_' };

static const int gPWBase64_dec[] = {
	 -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
	 -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
	 -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,	-1,  -1,  -1,  62,  -1,  62,  -1,  63,
	 52,  53,  54,  55,  56,  57,  58,  59,	60,  61,  -1,  -1,  -1,  -1,  -1,  -1,
	 -1,   0,   1,   2,   3,   4,   5,   6,	 7,   8,   9,  10,  11,  12,  13,  14,
	 15,  16,  17,  18,  19,  20,  21,  22,	23,  24,  25,  -1,  -1,  -1,  -1,  63,
	 -1,  26,  27,  28,  29,  30,  31,  32,	33,  34,  35,  36,  37,  38,  39,  40,
	 41,  42,  43,  44,  45,  46,  47,  48,	49,  50,  51,  -1,  -1,  -1,  -1,  -1,
	 -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
	 -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
	 -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
	 -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
	 -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
	 -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
	 -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
	 -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,	-1,  -1,  -1,  -1,  -1,  -1,  -1,  -1 };



PWBase64&
PWBase64::getInstance(void)
{
	static PWBase64 inst;
	return inst;
}

PWBase64::PWBase64()
{
}

char*
PWBase64::encodeUnit(char o[4], const char* i, size_t ilen, bool isURL) const
{
	unsigned char* ibuf((unsigned char*)i);
	const char* tab(isURL?gPWBase64_encURI:gPWBase64_enc);

	if ( ilen > 2 )
	{
		o[0] = tab[ ibuf[0] >> 2 ];
		o[1] = tab[ ((ibuf[0] & 0x03) << 4) + (ibuf[1] >> 4) ];
		o[2] = tab[ ((ibuf[1] & 0x0f) << 2) + (ibuf[2] >> 6) ];
		o[3] = tab[ ibuf[2] & 0x3f ];
	}
	else if ( ilen == 2 )
	{
		o[0] = tab[ ibuf[0] >> 2 ];
		o[1] = tab[ ((ibuf[0] & 0x03) << 4) + (ibuf[1] >> 4) ];
		o[2] = tab[ ( ibuf[1] & 0x0f ) << 2 ];
		o[3] = '=';
	}
	else
	{
		o[0] = tab[ ibuf[0] >> 2 ];
		o[1] = tab[ (ibuf[0] & 0x03) << 4 ];
		o[2] = o[3] = '=';
	}

	return o;
}

size_t
PWBase64::decodeUnit(char o[3], const char i[4]) const
{
	unsigned char* ibuf((unsigned char*)i);
	int c;

	size_t idx(0);
	while ( idx < 4 )
	{
		c = ibuf[idx];
		if ( c == '=' )
		{
			break;
		}

		if ( c == ' ' )
		{
			c = '+';
		}

		if ( (c = gPWBase64_dec[c]) == -1 )
		{
			// ERROR
			return 0;
		}

		switch (idx)
		{
		case 0:
		{
			o[0] = c << 2;
			break;
		}
		case 1:
		{
			o[0] |= c >> 4;
			o[1] = (c&0x0f) << 4;
			break;
		}
		case 2:
		{
			o[1] |= c >> 2;
			o[2] = (c&0x03) << 6;
			break;
		}
		case 3:
		{
			o[2] |= c;
		}
		}// switch(idx)

		++idx;
	}// while ( idx<4 )

	// idx
	// 4 -> 3
	// 3 -> 2
	// 2 -> 1
	// 1 -> 0
	// 0 -> 0

	if ( idx < 2 ) return 0;
	return idx - 1;
}

size_t
PWBase64::encode(char* obuf, const char* ibuf, size_t iblen, bool isURI) const
{
	const size_t step_max(iblen/3);
	size_t step(0);

	const char* ib(ibuf);
	const char* ie(ibuf+iblen);
	char* it(obuf);

	//gLibLog.format("ibuf size: %zu step: %zu", iblen, step);

	while ( step < step_max )
	{
		encodeUnit(it, ib, 3, isURI);
		it += 4;
		ib += 3;
		++ step;
	}

	size_t ret(step*4);

	if ( ib != ie )
	{
		encodeUnit(it, ib, iblen%3, isURI);
		ret += 4;
	}

	return ret;
}

size_t
PWBase64::decode(char* obuf, const char* ibuf, size_t iblen) const
{
	const size_t step_max(iblen/4);
	size_t step(0);

	const char* ib(ibuf);
	//const char* ie(ibuf+iblen);
	char* it(obuf);
	size_t dec_len;

	while ( step < step_max )
	{
		dec_len = decodeUnit(it, ib);
		if ( 0 == dec_len )
		{
			// ERROR
			return 0;
		}

		it += dec_len;
		ib += 4;
		++ step;
	}

	return step*3;
}

std::ostream&
PWBase64::encode(std::ostream& os, std::istream& is, bool isURI) const
{
	char i[3];
	char o[4];
	size_t rsize;

	while ( !is.eof() )
	{
		is.read(i, 3);
		rsize = (size_t)is.gcount();
		if ( rsize == 0 )
		{
			continue;
		}

		encodeUnit(o, i, rsize, isURI);
		os.write(o, 4);
	}

	return os;
}

std::ostream&
PWBase64::decode(std::ostream& os, std::istream& is) const
{
	char i[4];
	char o[3];
	size_t rsize, dsize;
	size_t tsize(0);
	char* si(i);

	while ( !is.eof() )
	{
		is.read(si, 4 - tsize);
		rsize = (size_t)is.gcount();
		if ( rsize == 0 )
		{
			continue;
		}

		tsize += rsize;
		if ( tsize != 4 )
		{
			si += rsize;
			continue;
		}

		si = i;
		tsize = 0;

		dsize = decodeUnit(o, i);
		if ( 0 == dsize )
		{
			break;
		}

		os.write(o, dsize);
	}

	return os;
}

};//namespace purewell


